﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace sinetd.Protocols.Udp
{
    internal class Helper //sink for messages from udp
    {
        public static Dictionary<Socket, Helper> helper = new Dictionary<Socket, Helper>();
        public static Helper GetHelper(Socket socket)
        {
            if (!helper.ContainsKey(socket))
                helper[socket] = new Helper();
            return helper[socket];
        }

        public byte[] byteData = new byte[1024];

        public List<int> usedPorts = new List<int>();

        public int port;

        public ManualResetEvent mre = new ManualResetEvent(false);
        public ManualResetEvent mre2 = new ManualResetEvent(false);

        public byte[] Get()
        {
            mre.Reset();
            byte[] byteData2 = byteData;
            mre2.Set();
            return byteData2;
        }
    }

    public class Protocol : BaseProtocol, IProtocol
    {
        Socket socket;
        EndPoint epSender;
        EndPoint epClient;
        public override void ConfigDefault()
        {
            cfg.SetDefault("localport", "0");
            cfg.SetDefault("port", "0");
            cfg.SetDefault("host", "127.0.0.1");
        }

        public Protocol() : base("Udp") { }
        
        public Protocol(Socket client, EndPoint ep, string id) : base("Udp", id)
        {
            this.socket = client;
            this.epClient = ep;
        }

        #region IProtocol Members

        byte[] byteData = new byte[1024];

        public void Bind(AcceptClientHandler AcceptClient)
        {
            IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, cfg.GetInt32("port"));
            
            StartThread(() =>
            {
                try
                {
                    socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                    socket.Bind(ipEndPoint);

                }
                catch { TerminatePending = true; }

                while (!TerminatePending)
                {
                    Protocol p = null;
                    try
                    {
                        
                        Helper.GetHelper(socket).mre2.Set();
                        bool fuck = true;
                        while (fuck)
                        {
                            epSender = (EndPoint)ipEndPoint;

                            Helper.GetHelper(socket).mre2.WaitOne();
                            Helper.GetHelper(socket).byteData = new byte[1024];
                            socket.ReceiveFrom(Helper.GetHelper(socket).byteData, ref epSender);
                            Helper.GetHelper(socket).mre2.Reset();
                            IPEndPoint ipc = (IPEndPoint)(epSender);
                            Helper.GetHelper(socket).port = ipc.Port;

                            if (!Helper.GetHelper(socket).usedPorts.Contains(Helper.GetHelper(socket).port))
                            {
                                Helper.GetHelper(socket).usedPorts.Add(Helper.GetHelper(socket).port);
                                fuck = false;
                            }
                            else
                            {
                                Helper.GetHelper(socket).mre.Set();
                            }
                        }

                        p = new Protocol(socket, epSender, Id);
                    }
                    catch { }
                    if (p != null)
                    {
                        Helper.GetHelper(socket).mre.Set();
                        AcceptClient(p);
                    }
                }
            });
        }
        bool client = false;
        public bool Connect()
        {
            try
            {
                client = true;
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
                IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, cfg.GetInt32("port"));
                epClient = (EndPoint)ipEndPoint;
                return true;
            }
            catch
            {
                return false;
            }
        }

        public void Send(string msg)
        {
            byte[] message = System.Text.Encoding.Unicode.GetBytes(msg);
            socket.SendTo(message, epClient);   
        }

        public string Recv()
        {
            byteData = new byte[1024];

            if (client)
            {
                socket.ReceiveFrom(byteData, ref epClient);
            }
            else
            {
                bool fuck = true;
                while (fuck)
                {
                    Helper.GetHelper(socket).mre.WaitOne();

                    IPEndPoint ipc = (IPEndPoint)(epClient);
                    if (Helper.GetHelper(socket).port == ipc.Port)
                    {
                        fuck = false;
                    }
                }
                byteData = Helper.GetHelper(socket).Get();
            }

            string p = System.Text.Encoding.Unicode.GetString(byteData);
            return p.Trim().Split('\0')[0];
        }

        ManualResetEvent mre = new ManualResetEvent(false);

        public void Close()
        {
            if (socket != null)
            {
                Helper.GetHelper(socket).mre.Set();
                Helper.GetHelper(socket).mre2.Set();
                socket.Close();
            }
            StopThread();
        }

        #endregion
    }
}
